<?php
/**
 * @desc db class file
 * @author nana
 * @date 2011
 * @need transaction and databaseresult class, DIR_CORE.'db'.DS.'database.php';
 *
 */

if (class_exists('PDO'))
{
	class NICED_PDO extends PDO implements NICED_Database
	{
		const ERROR_CODE_NONE = '00000';
        public static $retry         = 3;

		private $dsn;
        private $dbSetting;
		private $sqlStatement;
		private $queries = array();
		private $queryCount = 0;
		private $lastQueryTime;
		private $connected = false;

		private $debug = false;
		private $maxQueryLogCount = 10;
		private $slowQueryThreshold = 0.1;
		private $logSlow       = true;

		public function __construct($dsn, $db_setting = NULL) 
		{
			$this->dsn = $dsn;

            if (!$db_setting)
            {
                $db_setting = NICED_DBFactory::getDsnSetting($this->dsn);
            }

            if (!$db_setting)
            {
                throw new NICED_DatabaseException('no db setting for dsn:'.$this->dsn);
            }

            $this->dbSetting = $db_setting;

            $this->initConfig();
		}

        private function initConfig()
        {
            $this->debug              = LC::get("database.pdo.debug") && SC::get("board_config.debug");
            $this->maxQueryLogCount   = LC::get("database.pdo.max_query_log_count");
            $this->slowQueryThreshold = LC::get("database.pdo.slow_query_threshold");
            $this->logSlow            = LC::get("database.pdo.log_slow");
            $this->retry              = LC::get("database.pdo.retry");
        }

		public function query($sql) 
		{
			$this->beginQuery($sql);
			$start = microtime(true);
			$rst = parent::query($sql);
			$end = microtime(true);
			$this->endQuery($sql);

			//get result
			if (!$rst) 
			{
				throw new NICED_DatabaseException("db execute query fail, error from db server:".$this->error.", sql is : ".$sql);
			}
			$result = new NICED_PDOResult($this, $rst, $sql);


			//log query
			$this->addQuery($result, $start, $end);

			return $result;
		}

		public function prepare($sql)
		{
			$this->beginQuery($sql);
			$stmt = parent::prepare($sql);
			$this->endQuery($sql);
			$this->addQuery(NULL, NULL, NULL);

			return $stmt;
		}

		public function beginQuery($sql)
		{
			$this->sqlStatement = $sql;

			if (!$this->isConnected()) 
			{
				$this->getConnect();
				if (!$this->isConnected())
				{
					throw new NICED_DatabaseException("oonnect to db fail！");
				}
			}		
		}

		public function endQuery($sql)
		{
			$this->lastQueryTime = time();	
		}

		public function getConnect() 
		{
            $db_setting = &$this->dbSetting;
    
            if ((isset($db_setting["use_failover"]) && $db_setting["use_failover"]))
            {
                $db_setting = array_merge($db_setting, $db_setting["failover_server"]);
            }

            $type   = isset($db_setting['type'])    ? $db_setting['type']    : '';
            $ip     = isset($db_setting['ip'])      ? $db_setting['ip']      : '';
            $port   = isset($db_setting['port'])    ? $db_setting['port']    : '';
            $dbname = isset($db_setting['dbname'])  ? $db_setting['dbname']  : '';
            $user   = isset($db_setting['user'])    ? $db_setting['user']    : '';
            $pass   = isset($db_setting['pass'])    ? $db_setting['pass']    : ''; 
            $charset= isset($db_setting['charset']) ? $db_setting['charset'] : ''; 
            $unix_socket = isset($db_setting["unix_socket"]) ? $db_setting["unix_socket"] : "";

            //build dsn
			$dsn    = $type.':host='.$ip.';dbname='.$dbname.';port='.$port.";charset=".$charset;
            if ($unix_socket)
            {
                $dsn .= ";unix_socket=".$unix_socket;
            }

            $options = array();
            if (isset($db_setting["timeout"]))
            {
                $options = array(PDO::ATTR_TIMEOUT => (int)$db_setting["timeout"]);
            }

            if($charset && version_compare(PHP_VERSION, '5.3.6', '<') && defined('PDO::MYSQL_ATTR_INIT_COMMAND'))
            {
                $options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES '.$charset;
            }                
			
			try
			{
				parent::__construct($dsn, $user, $pass, $options);

				if (($error_code = $this->errorCode()) && $error_code != self::ERROR_CODE_NONE)
				{
					throw new NICED_DatabaseException("db error！".$this->getErrorMessage());
				}

				$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
			    $this->connected = true;

				if (true || $charset && version_compare(PHP_VERSION, '5.3.6', '<') && !defined('PDO::MYSQL_ATTR_INIT_COMMAND'))
				{
					$this->query("SET NAMES ".$charset);
				}
			} 
			catch (Exception $e) //if connect db fail, will throw exception, and pdo obj will be NULL
			{
				throw new NICED_DatabaseException($e->getMessage(), $e->getCode());
			}

			return $this;
		}

		private function isConnected() 
		{
			return $this->connected;
		}

		private function addQuery($rs, $start = NULL, $end = NULL)
		{
			if(!$this->debug) 
			{
				//return;
			}

			$this->queryCounter++;

			if ($this->logSlow && (($interval = $end - $start) > $this->slowQueryThreshold))
			{
				NICED_LOG(LC::get("database.pdo.log_level"), NICED.' slow query log from pdo: '.$this->sqlStatement.' exec time is '.$interval);

                //alarm
                NICED_ALARM("pdo_slow_query");
			}

			if($this->queryCount > $this->maxQueryLogCount) 
			{
				$this->debug = false;
			}

            $this->queries[] = array(
                'type'=>'query',
                'count'=>$this->queryCounter, 
                'rs'=>$rs, 
                'start'=>$start, 
                'end'=>$end, 
                //'backtrace'=> debug_backtrace(),
            );
		}

		public function createTransaction()
		{
			return new NICED_MysqlTransaction($this);
		}

        public function logQueries()
        {
            $sql_log_str = "";
            foreach ($this->queries as $v)
            {
                $rs = $v["rs"];
                $sql = $rs->getSQL();
                $sql_log_str .= $sql;//.", db setting is : ".var_export($rs->getDB()->getDBSetting(), true).";";
            }

            NICED_LOG(LC::get("database.pdo.log_level"), NICED." all sql is ".$sql_log_str.", driver is ".__CLASS__);
        }

		public function close()
		{
            $this->logQueries();		
		}

		public function getErrorMessage()
		{
			return implode('  ', $this->errorInfo());
		}

        public function getDBSetting()
        {
            return $this->dbSetting;
        }

	}//end class
}

